package engine;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import javax.swing.SwingWorker;

import util.PGNWriter;

import Polyglot.PgKey;
import Polyglot.PgShow;
import beans.AnalysisInformation;
import beans.Color;
import beans.Move;
import board.Board;
import board.Game;

/**
 * This class can be used to perform a game analysis with a concrete chess engine, given certain parameters
 * 
 * @author Josep M Sobrepere
 *
 */
public class Analyzer  extends SwingWorker<Void, Void>{
	private Game game;
	private ChessEngine engine;
	private List<File> books;
	private int totalTime;
	private int limitError; 
	private int noAnalysisBorder; 
	private int maxNumberMistakesToAnalize;
	private String fileDestination;
	
	/**
	 * Constructor
	 * 
	 * @param game the game to be analyzed 
	 * @param engine the engine that will be used to analyze the game
	 * @param books the polyglot books that will be used to perform the analysis
	 * @param totalTime the total amount of time that will be dedicated to the analysis
	 * @param limitError the number of centipawns that will be considered an error
	 * @param noAnalysisBorder the number of centipawns beyond where the deep analysis is not recuried 
	 * @param maxNumberMistakesToAnalize the maximum of mistakes to be analyzed
	 * @param fileDestination the file where the analysis will be saved 
	 */
	public Analyzer(Game game, ChessEngine engine, List<File> books, int totalTime, int limitError, int noAnalysisBorder, int maxNumberMistakesToAnalize, String fileDestination) {
		this.game= game;
		this.engine = engine;
		this.books=books;
		this.totalTime=totalTime;
		this.limitError=limitError;
		this.noAnalysisBorder=noAnalysisBorder;
		this.maxNumberMistakesToAnalize=maxNumberMistakesToAnalize;
		this.fileDestination=fileDestination;
	}
	
	
	
	/**
	 * the function that performs the main analysis of the moves
	 * @param timeAvailable
	 * @throws IOException
	 */
	private void analysis(int timeAvailable) throws IOException{		
		long start = new Date().getTime();
		long now=0;
		int timeLeft = timeAvailable;
		int progress =0;

		String EMS=" ";
		
		Move nextExpectedMove = null;
		Board board = new Board();
		
		Iterator<Move> it = game.getMoves().iterator(); 
		
		
		int bookMoves=0;
		Move lastBookMove=null;
		do{
			Move move =it.next();
			board.registerMove(move);
			if(isBook(board.getFEN())){
				move.setAnalysis(new AnalysisInformation(move, 0, 0, 0, true, null, 0, 0)) ;				
				lastBookMove=move;				
			}
		}while(it.hasNext());
		if(lastBookMove!=null){
			it = game.getMoves().iterator();
			Move move;
			do{
				move =it.next();
				move.setAnalysis(new AnalysisInformation(move, 0, 0, 0, true, null, 0, 0)) ;
				bookMoves++;
			}while(move!=lastBookMove);
		}
		
		
		int nMoves = game.getMoves().size()-bookMoves;
		if(game.getMoves().get(game.getMoves().size()-1).isMate())
			nMoves--;
		
		timeLeft = (int) (timeAvailable- (new Date().getTime() - start));
		int timeXMove = timeLeft/nMoves;
		int count=0;
		
		this.setProgress(1);
						
		for(Move mv:game.getMoves()){
			if(mv.getAnalysis()==null && !mv.isMate()){
				AnalysisInformation info = engine.analyzeMove(mv, timeXMove);
				mv.setAnalysis(info);
				count++;
				progress = (980000/nMoves)*count;
				progress = progress/10000;
				this.setProgress(progress);
			}			
			//this.setProgress(progress);
		}
		
		/*do{
			AnalysisInformation info = engine.analyzeMove(move, timeXMove);
			move.setAnalysis(info);			
			move=move.getNextMove();
			count++;
		}while(count<nMoves);*/
		
		/*for(Move move: game.getMoves()){
			board.registerMove(move);
			
			
			if(!move.equals(nextExpectedMove) && move.getPreviousMove()!=null){				
				AnalysisInformation bestMoveInfo=null;
				
				if(move.getPreviousMove().getAnalysis()==null){
					bestMoveInfo = engine.analyzeMove(move.getPreviousMove(), timeXMove/2);				
					move.getPreviousMove().setAnalysis(bestMoveInfo);					
				}else{
					bestMoveInfo =move.getPreviousMove().getAnalysis();
				}
				
				if(!bestMoveInfo.getBestContinuation().get(0).equals(move)){
					AnalysisInformation info = engine.analyzeMove(move, timeXMove/2);
					move.setAnalysis(info);					
					nextExpectedMove = info.getBestContinuation().get(0);
				}else{
					nextExpectedMove = bestMoveInfo.getBestContinuation().get(1);
				}				
			}else{
				if(nextExpectedMove!=null)
					nextExpectedMove = nextExpectedMove.getNextMove();				
			}
		}*/
	}
	/**
	 * This function can be used to know if a position is a book position given a FEN position
	 * @param FEN the position in FEN format
	 * @return if the position is a book position
	 */
	private boolean isBook(String FEN){
		String [] fen = FEN.split(" ");
		for(File book: books){
			if (PgShow.getEntriesFromKey(book, PgKey.hash(fen[0].toCharArray(), fen[1].charAt(0), fen[2].toCharArray(), fen[3].toCharArray())).size()>0)				
				return true;			
		}
		return false;		
	}

	@Override
	protected Void doInBackground() throws Exception {
		engine.start();
		
		Date start = new Date();
		
		this.setProgress(0);
		
		analysis(totalTime);
		
		int currentScore=0;
		
		
		ArrayList <AnalysisInformation> movesToBeAnalyzed = new ArrayList<AnalysisInformation>();
		
		Move move = game.getMoves().getFirst();
		do{
			int turn=1;
			if(move.getAnalysis()!=null && !move.getAnalysis().isBookMove()){
				turn = (move.getPiece().getColor()==Color.WHITE)?1:-1;
				if(move.getAnalysis().getScore()*turn<currentScore*turn){
					move.getAnalysis().setDifferenceScoreBestMove(Math.abs(move.getAnalysis().getScore() - currentScore));				
					if(move.getAnalysis().getDifferenceScoreBestMove()>limitError && ((Math.abs(currentScore)<noAnalysisBorder && Math.abs(move.getAnalysis().getScore())<noAnalysisBorder) || (move.getAnalysis().getDifferenceScoreBestMove()+Math.abs(currentScore)) !=Math.abs(move.getAnalysis().getScore())) && move.getPreviousMove()!=null){
						movesToBeAnalyzed.add(move.getAnalysis());
					}
				}else{
					//una molt bona jugada
					move.getAnalysis().setDifferenceScoreBestMove(Math.abs(move.getAnalysis().getScore() - currentScore)*-1);
				}
				currentScore = move.getAnalysis().getScore();
			}		
			move = move.getNextMove();
		}while(move!=null);
		
		Collections.sort(movesToBeAnalyzed);
		
		if(movesToBeAnalyzed.size()<maxNumberMistakesToAnalize)
			maxNumberMistakesToAnalize = movesToBeAnalyzed.size();
		
		//int timeLeft = totalTime - (int) ((new Date()).getTime()-start.getTime());
		
		//int progress =98;
		this.setProgress(98);
		
		for (int i=0; i<maxNumberMistakesToAnalize; i++){
			//movesToBeAnalyzed.get(i).getMove().getPreviousMove().setAnalysis(engine.analyzeMove(movesToBeAnalyzed.get(i).getMove().getPreviousMove(), (int) ((timeLeft/maxNumberMistakesToAnalize))));
			//movesToBeAnalyzed.get(i).getMove().setAnalysis(engine.analyzeMove(movesToBeAnalyzed.get(i).getMove(), (int) ((timeLeft/maxNumberMistakesToAnalize)*0.5)));
			movesToBeAnalyzed.get(i).getMove().getPreviousMove().getAnalysis().setDeeplyAnalized(true);
			
			/*progress = 98+((2/maxNumberMistakesToAnalize)*(i+1));
			this.setProgress(progress);*/
		}
		
		currentScore=0;
		
		/* move = game.getMoves().getFirst();
		
		do{
			if(move.getAnalysis()!=null){
				move.getAnalysis().setDifferenceScoreBestMove(Math.abs(move.getAnalysis().getScore() - currentScore));
				currentScore = move.getAnalysis().getScore();
			}
			move= move.getNextMove();
		}while(move!=null);*/
		
		PGNWriter.writeFile(game, fileDestination);
		
		this.setProgress(100);
		engine.stop();		
		
		/*if(serializePath!=null){
			try {
				FileOutputStream fs = new FileOutputStream(serializePath);
				ObjectOutputStream os = new ObjectOutputStream(fs);
				os.writeObject(game);
				os.close();
			} catch (Exception e) { e.printStackTrace(); }
		}*/
		
		
		return null;
		
		
	}
}